
package com.gitee.jmash.rbac.service;

import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import com.gitee.jmash.common.lock.DistributedLock;
import com.gitee.jmash.common.utils.UUIDUtil;
import com.gitee.jmash.core.jaxrs.ParamsValidationException;
import com.gitee.jmash.core.orm.cdi.JpaTenantService;
import com.gitee.jmash.core.transaction.JakartaTransaction;
import com.gitee.jmash.core.utils.FieldMaskUtil;
import com.gitee.jmash.rbac.dao.PermDao;
import com.gitee.jmash.rbac.dao.UsersRolesDao;
import com.gitee.jmash.rbac.entity.PermEntity;
import com.gitee.jmash.rbac.entity.RoleEntity;
import com.gitee.jmash.rbac.entity.RolesPermsEntity;
import com.gitee.jmash.rbac.entity.RolesPermsEntity.RolesPermsPk;
import com.gitee.jmash.rbac.entity.UsersRolesEntity;
import com.gitee.jmash.rbac.entity.UsersRolesEntity.UsersRolesPk;
import com.gitee.jmash.rbac.mapper.RoleMapper;
import jakarta.enterprise.inject.Typed;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import jakarta.transaction.Transactional.TxType;
import jakarta.validation.ValidationException;
import jakarta.validation.executable.ValidateOnExecution;
import jmash.rbac.protobuf.AppendRolePermReq;
import jmash.rbac.protobuf.RoleCreateReq;
import jmash.rbac.protobuf.RoleMoveKey;
import jmash.rbac.protobuf.RoleType;
import jmash.rbac.protobuf.RoleUpdateReq;

/**
 * 角色/职务表 rbac_role写服务.
 *
 * @author <a href="mailto:service@crenjoy.com">crenjoy</a>
 */
@Typed(RoleWrite.class)
@Transactional(TxType.REQUIRED)
@JpaTenantService
@ValidateOnExecution
public class RoleWriteBean extends RoleReadBean implements RoleWrite, JakartaTransaction {

  @Inject
  DistributedLock lock;

  protected PermDao permDao = new PermDao(this.tem);
  protected UsersRolesDao userRoleDao = new UsersRolesDao(this.tem);

  @Override
  @PersistenceContext(unitName = "WriteRbac")
  public void setEntityManager(EntityManager entityManager) {
    this.tem.setEntityManager(entityManager, true);
  }

  @Override
  public RoleEntity insert(RoleCreateReq role) {
    RoleEntity entity = RoleMapper.INSTANCE.create(role);
    // 1.业务校验.
    // 2.仅校验,不执行.
    if (role.getValidateOnly()) {
      return entity;
    }
    // 3.检查是否重复请求.
    if (!lock.lock(role.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }

    // 设置ParentId,深度和排序
    roleDao.insertTree(entity, UUIDUtil.fromString(role.getParentId()),
        Collections.singletonMap("roleType", role.getRoleType()));

    // 4.执行业务(创建人及时间内部处理.)
    roleDao.persist(entity);
    // 角色权限
    Set<UUID> permIdSet = permDao.codesToIds(role.getPermCodesList());
    rolesPermsDao.updateRolesPerms(entity.getRoleId(), permIdSet);
    // 角色职责分离
    dutyDao.updateRoleDuty(entity.getRoleId(), role.getRoleDutyList());
    return entity;
  }

  @Override
  public RoleEntity update(RoleUpdateReq req) {
    RoleEntity entity = roleDao.find(UUIDUtil.fromString(req.getRoleId()), req.getValidateOnly());
    if (null == entity) {
      throw new ValidationException("找不到实体:" + req.getRoleId());
    }

    // 无需更新,返回当前数据库数据.
    if (req.getUpdateMask().getPathsCount() == 0) {
      return entity;
    }
    // 放到FieldMaskUtil.copyMask前
    UUID newParentId = UUIDUtil.fromString(req.getParentId());
    UUID oldParentId = entity.getParentId();
    // 更新掩码属性
    FieldMaskUtil.copyMask(entity, req, req.getUpdateMask());
    // 1.业务校验.
    // 2.仅校验,不执行.
    if (req.getValidateOnly()) {
      return entity;
    }

    // 3.检查是否重复请求.
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    if (StringUtils.isBlank(req.getParentId())) {
      entity.setParentId(UUIDUtil.emptyUUID());
    }

    // 更新ParentId,深度和排序
    roleDao.updateTree(entity, oldParentId, newParentId,
        Collections.singletonMap("roleType", req.getRoleType()));
    // 4.执行业务
    roleDao.merge(entity);

    // 角色权限
    Set<UUID> permIdSet = permDao.codesToIds(req.getPermCodesList());
    rolesPermsDao.updateRolesPerms(entity.getRoleId(), permIdSet);
    // 角色职责分离
    dutyDao.updateRoleDuty(entity.getRoleId(), req.getRoleDutyList());
    return entity;
  }

  @Override
  public RoleEntity delete(UUID entityId) {
    boolean isRelation = this.isRelationRole(entityId);
    if (isRelation) {
      throw new ValidationException("数据存在关联，不能删除");
    } else {
      RoleEntity entity = roleDao.removeById(entityId);
      rolesPermsDao.removeByRoleId(entityId);
      dutyDao.removeByRoleId(entityId);
      return entity;
    }
  }

  @Override
  public Integer batchDelete(Set<UUID> entityIds) {
    int i = 0;
    for (UUID entityId : entityIds) {
      boolean isRelation = this.isRelationRole(entityId);
      if (!isRelation) {
        rolesPermsDao.removeByRoleId(entityId);
        dutyDao.removeByRoleId(entityId);
        roleDao.removeById(entityId);
        i++;
      }
    }
    return i;
  }

  /**
   * 判断有无关联角色/岗位.
   */
  public boolean isRelationRole(UUID entityId) {
    RoleEntity entity = roleDao.find(entityId);
    int count = 0;
    if (entity.getRoleType().equals(RoleType.role)) {
      // 角色关联判断
      count = userRoleDao.isRelationRole(entityId);
      if (count > 0) {
        return true;
      }
    } else {
      // 岗位关联判断
      int relationCount = usersJobsDao.hasExistJobByJobId(entityId);
      if (relationCount > 0) {
        return true;
      }
    }
    return false;
  }

  @Override
  public boolean moveOrderBy(RoleMoveKey roleMoveKey) {
    UUID entityId = UUIDUtil.fromString(roleMoveKey.getRoleId());
    RoleEntity entity = findById(entityId);
    if (null == entity) {
      throw new ValidationException("找不到实体:" + entityId.toString());
    }
    return roleDao.moveOrderBy(roleMoveKey.getUp(), entity,
        Collections.singletonMap("roleType", entity.getRoleType()));
  }

  @Override
  public Integer assignUser(UUID userId, Set<String> roleCodes) {
    int i = 0;
    for (String roleCode : roleCodes) {
      RoleEntity entity = roleDao.findByCode(roleCode);
      if (null != entity) {
        userRoleDao.merge(new UsersRolesEntity(userId, entity.getRoleId()));
        i++;
      }
    }
    return i;
  }

  @Override
  public Integer deassignUser(UUID userId, Set<String> roleCodes) {
    int i = 0;
    for (String roleCode : roleCodes) {
      RoleEntity entity = roleDao.findByCode(roleCode);
      if (null != entity) {
        userRoleDao
            .removeById(new UsersRolesPk(userId, entity.getRoleId()));
        i++;
      }
    }
    return i;
  }

  @Override
  public boolean appendRolePerm(AppendRolePermReq req) {
    RoleEntity entity = roleDao.find(UUID.fromString(req.getRoleId()));
    if (null == entity) {
      throw new ValidationException("找不到实体:" + req.getRoleId());
    }
    if (!lock.lock(req.getRequestId(), 60)) {
      throw new ParamsValidationException("requestId", "客户端发起重复请求");
    }
    // 新增权限编码，并根据前缀过滤
    Set<String> newPermCodes = req.getPermCodesList().stream()
        .filter(s -> s.startsWith(req.getPermCodePrefix()))
        .collect(Collectors.toSet());
    // 已有角色权限列表
    List<String> rolePermCodes = permDao.findRolePerms(entity.getRoleId());

    // 移除未选择的权限
    for (String permCode : rolePermCodes) {
      // 仅考虑前缀权限
      if (permCode.startsWith(req.getPermCodePrefix())) {
        if (newPermCodes.contains(permCode)) {
          continue;
        }
        // 角色权限ID
        PermEntity permEntity = permDao.findByCode(permCode);
        if (permEntity != null) {
          RolesPermsPk pk = new RolesPermsPk(entity.getRoleId(), permEntity.getPermId());
          rolesPermsDao.removeById(pk);
        }
      }
    }
    // 过滤掉已存在的权限
    List<String> filterPermCodes = newPermCodes.stream()
        .filter(permCode -> !rolePermCodes.contains(permCode)).toList();
    Set<UUID> permIdSet = permDao.codesToIds(filterPermCodes);
    // 添加新增的权限编码
    for (UUID permId : permIdSet) {
      RolesPermsEntity rolesPermsEntity = new RolesPermsEntity(entity.getRoleId(), permId);
      rolesPermsDao.persist(rolesPermsEntity);
    }
    return true;
  }
}
